/* sig-check.c - Check signatures
 * Copyright (C) 1998-2012 Free Software Foundation, Inc.
 *
 * Author: Timo Schulz
 *
 * This file is part of OpenCDK.
 *
 * The OpenCDK library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <assert.h>

#include "opencdk.h"
#include "main.h"
#include "packet.h"

/* Hash all multi precision integers of the key PK with the given
   message digest context MD. */
static int
hash_mpibuf (cdk_pubkey_t pk, digest_hd_st * md, int usefpr)
{
  byte buf[MAX_MPI_BYTES];      /* FIXME: do not use hardcoded length. */
  size_t nbytes;
  size_t i, npkey;
  int err;

  /* We have to differ between two modes for v3 keys. To form the
     fingerprint, we hash the MPI values without the length prefix.
     But if we calculate the hash for verifying/signing we use all data. */
  npkey = cdk_pk_get_npkey (pk->pubkey_algo);
  for (i = 0; i < npkey; i++)
    {
      nbytes = MAX_MPI_BYTES;
      err = _gnutls_mpi_print_pgp (pk->mpi[i], buf, &nbytes);

      if (err < 0)
        {
          gnutls_assert ();
          return map_gnutls_error (err);
        }

      if (!usefpr || pk->version == 4)
        _gnutls_hash (md, buf, nbytes);
      else                      /* without the prefix. */
        _gnutls_hash (md, buf + 2, nbytes - 2);
    }
  return 0;
}


/* Hash an entire public key PK with the given message digest context
   MD. The @usefpr param is only valid for version 3 keys because of
   the different way to calculate the fingerprint. */
cdk_error_t
_cdk_hash_pubkey (cdk_pubkey_t pk, digest_hd_st * md, int usefpr)
{
  byte buf[12];
  size_t i, n, npkey;

  if (!pk || !md)
    return CDK_Inv_Value;

  if (usefpr && pk->version < 4 && is_RSA (pk->pubkey_algo))
    return hash_mpibuf (pk, md, 1);

  /* The version 4 public key packet does not have the 2 octets for
     the expiration date. */
  n = pk->version < 4 ? 8 : 6;
  npkey = cdk_pk_get_npkey (pk->pubkey_algo);
  for (i = 0; i < npkey; i++)
    n = n + (_gnutls_mpi_get_nbits (pk->mpi[i]) + 7) / 8 + 2;

  i = 0;
  buf[i++] = 0x99;
  buf[i++] = n >> 8;
  buf[i++] = n >> 0;
  buf[i++] = pk->version;
  buf[i++] = pk->timestamp >> 24;
  buf[i++] = pk->timestamp >> 16;
  buf[i++] = pk->timestamp >> 8;
  buf[i++] = pk->timestamp >> 0;

  if (pk->version < 4)
    {
      u16 a = 0;

      /* Convert the expiration date into days. */
      if (pk->expiredate)
        a = (u16) ((pk->expiredate - pk->timestamp) / 86400L);
      buf[i++] = a >> 8;
      buf[i++] = a;
    }
  buf[i++] = pk->pubkey_algo;
  _gnutls_hash (md, buf, i);
  return hash_mpibuf (pk, md, 0);
}


/* Hash the user ID @uid with the given message digest @md.
   Use openpgp mode if @is_v4 is 1. */
cdk_error_t
_cdk_hash_userid (cdk_pkt_userid_t uid, int is_v4, digest_hd_st * md)
{
  const byte *data;
  byte buf[5];
  u32 dlen;

  if (!uid || !md)
    return CDK_Inv_Value;

  if (!is_v4)
    {
      _gnutls_hash (md, (byte *) uid->name, uid->len);
      return 0;
    }

  dlen = uid->attrib_img ? uid->attrib_len : uid->len;
  data = uid->attrib_img ? uid->attrib_img : (byte *) uid->name;
  buf[0] = uid->attrib_img ? 0xD1 : 0xB4;
  buf[1] = dlen >> 24;
  buf[2] = dlen >> 16;
  buf[3] = dlen >> 8;
  buf[4] = dlen >> 0;
  _gnutls_hash (md, buf, 5);
  _gnutls_hash (md, data, dlen);
  return 0;
}


/* Hash all parts of the signature which are needed to derive
   the correct message digest to verify the sig. */
cdk_error_t
_cdk_hash_sig_data (cdk_pkt_signature_t sig, digest_hd_st * md)
{
  byte buf[4];
  byte tmp;

  if (!sig || !md)
    return CDK_Inv_Value;

  if (sig->version == 4)
    _gnutls_hash (md, &sig->version, 1);

  _gnutls_hash (md, &sig->sig_class, 1);
  if (sig->version < 4)
    {
      buf[0] = sig->timestamp >> 24;
      buf[1] = sig->timestamp >> 16;
      buf[2] = sig->timestamp >> 8;
      buf[3] = sig->timestamp >> 0;
      _gnutls_hash (md, buf, 4);
    }
  else
    {
      size_t n;

      tmp = _cdk_pub_algo_to_pgp (sig->pubkey_algo);
      _gnutls_hash (md, &tmp, 1);
      tmp = _gnutls_hash_algo_to_pgp (sig->digest_algo);
      _gnutls_hash (md, &tmp, 1);
      if (sig->hashed != NULL)
        {
          byte *p = _cdk_subpkt_get_array (sig->hashed, 0, &n);
          assert (p != NULL);
          buf[0] = n >> 8;
          buf[1] = n >> 0;
          _gnutls_hash (md, buf, 2);
          _gnutls_hash (md, p, n);
          cdk_free (p);
          sig->hashed_size = n;
          n = sig->hashed_size + 6;
        }
      else
        {
          tmp = 0x00;
          _gnutls_hash (md, &tmp, 1);
          _gnutls_hash (md, &tmp, 1);
          n = 6;
        }
      _gnutls_hash (md, &sig->version, 1);
      tmp = 0xff;
      _gnutls_hash (md, &tmp, 1);
      buf[0] = n >> 24;
      buf[1] = n >> 16;
      buf[2] = n >> 8;
      buf[3] = n >> 0;
      _gnutls_hash (md, buf, 4);
    }
  return 0;
}


/* Cache the signature result and store it inside the sig. */
static void
cache_sig_result (cdk_pkt_signature_t sig, int res)
{
  sig->flags.checked = 0;
  sig->flags.valid = 0;
  if (res == 0)
    {
      sig->flags.checked = 1;
      sig->flags.valid = 1;
    }
  else if (res == CDK_Bad_Sig)
    {
      sig->flags.checked = 1;
      sig->flags.valid = 0;
    }
}


/* Check the given signature @sig with the public key @pk.
   Use the digest handle @digest. */
cdk_error_t
_cdk_sig_check (cdk_pubkey_t pk, cdk_pkt_signature_t sig,
                digest_hd_st * digest, int *r_expired)
{
  cdk_error_t rc;
  byte md[MAX_DIGEST_LEN];
  time_t cur_time = (u32) gnutls_time (NULL);

  if (!pk || !sig || !digest)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }

  if (sig->flags.checked)
    return sig->flags.valid ? 0 : CDK_Bad_Sig;
  if (!KEY_CAN_SIGN (pk->pubkey_algo))
    return CDK_Inv_Algo;
  if (pk->timestamp > sig->timestamp || pk->timestamp > cur_time)
    return CDK_Time_Conflict;

  if (r_expired && pk->expiredate
      && (pk->expiredate + pk->timestamp) > cur_time)
    *r_expired = 1;

  _cdk_hash_sig_data (sig, digest);
  _gnutls_hash_output (digest, md);

  if (md[0] != sig->digest_start[0] || md[1] != sig->digest_start[1])
    {
      gnutls_assert ();
      return CDK_Chksum_Error;
    }

  rc = cdk_pk_verify (pk, sig, md);
  cache_sig_result (sig, rc);
  return rc;
}


/* Check the given key signature.
   @knode is the key node and @snode the signature node. */
cdk_error_t
_cdk_pk_check_sig (cdk_keydb_hd_t keydb,
                   cdk_kbnode_t knode, cdk_kbnode_t snode, int *is_selfsig,
                   char **ret_uid)
{
  digest_hd_st md;
  int err;
  cdk_pubkey_t pk;
  cdk_pkt_signature_t sig;
  cdk_kbnode_t node;
  cdk_error_t rc = 0;
  int is_expired;

  if (!knode || !snode)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }

  if (is_selfsig)
    *is_selfsig = 0;
  if ((knode->pkt->pkttype != CDK_PKT_PUBLIC_KEY &&
       knode->pkt->pkttype != CDK_PKT_PUBLIC_SUBKEY) ||
      snode->pkt->pkttype != CDK_PKT_SIGNATURE)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }
  pk = knode->pkt->pkt.public_key;
  sig = snode->pkt->pkt.signature;

  err = _gnutls_hash_init (&md, sig->digest_algo);
  if (err < 0)
    {
      gnutls_assert ();
      return map_gnutls_error (err);
    }

  is_expired = 0;
  if (sig->sig_class == 0x20)
    {                           /* key revocation */
      cdk_kbnode_hash (knode, &md, 0, 0, 0);
      rc = _cdk_sig_check (pk, sig, &md, &is_expired);
    }
  else if (sig->sig_class == 0x28)
    {                           /* subkey revocation */
      node = cdk_kbnode_find_prev (knode, snode, CDK_PKT_PUBLIC_SUBKEY);
      if (!node)
        {                       /* no subkey for subkey revocation packet */
          gnutls_assert ();
          rc = CDK_Error_No_Key;
          goto fail;
        }
      cdk_kbnode_hash (knode, &md, 0, 0, 0);
      cdk_kbnode_hash (node, &md, 0, 0, 0);
      rc = _cdk_sig_check (pk, sig, &md, &is_expired);
    }
  else if (sig->sig_class == 0x18 || sig->sig_class == 0x19)
    {                           /* primary/secondary key binding */
      node = cdk_kbnode_find_prev (knode, snode, CDK_PKT_PUBLIC_SUBKEY);
      if (!node)
        {                       /* no subkey for subkey binding packet */
          gnutls_assert ();
          rc = CDK_Error_No_Key;
          goto fail;
        }
      cdk_kbnode_hash (knode, &md, 0, 0, 0);
      cdk_kbnode_hash (node, &md, 0, 0, 0);
      rc = _cdk_sig_check (pk, sig, &md, &is_expired);
    }
  else if (sig->sig_class == 0x1F)
    {                           /* direct key signature */
      cdk_kbnode_hash (knode, &md, 0, 0, 0);
      rc = _cdk_sig_check (pk, sig, &md, &is_expired);
    }
  else
    {                           /* all other classes */
      cdk_pkt_userid_t uid;
      node = cdk_kbnode_find_prev (knode, snode, CDK_PKT_USER_ID);
      if (!node)
        {                       /* no user ID for key signature packet */
          gnutls_assert ();
          rc = CDK_Error_No_Key;
          goto fail;
        }

      uid = node->pkt->pkt.user_id;
      if (ret_uid)
        {
          *ret_uid = uid->name;
        }
      cdk_kbnode_hash (knode, &md, 0, 0, 0);
      cdk_kbnode_hash (node, &md, sig->version == 4, 0, 0);

      if (pk->keyid[0] == sig->keyid[0] && pk->keyid[1] == sig->keyid[1])
        {
          rc = _cdk_sig_check (pk, sig, &md, &is_expired);
          if (is_selfsig)
            *is_selfsig = 1;
        }
      else if (keydb != NULL)
        {
          cdk_pubkey_t sig_pk;
          rc = cdk_keydb_get_pk (keydb, sig->keyid, &sig_pk);
          if (!rc)
            rc = _cdk_sig_check (sig_pk, sig, &md, &is_expired);
          cdk_pk_release (sig_pk);
        }
    }
fail:
  _gnutls_hash_deinit (&md, NULL);
  return rc;
}

struct verify_uid
{
  const char *name;
  int nsigs;
  struct verify_uid *next;
};

static int
uid_list_add_sig (struct verify_uid **list, const char *uid,
                  unsigned int flag)
{
  if (*list == NULL)
    {
      *list = cdk_calloc (1, sizeof (struct verify_uid));
      if (*list == NULL)
        return CDK_Out_Of_Core;
      (*list)->name = uid;

      if (flag != 0)
        (*list)->nsigs++;
    }
  else
    {
      struct verify_uid *p, *prev_p = NULL;
      int found = 0;

      p = *list;

      while (p != NULL)
        {
          if (strcmp (uid, p->name) == 0)
            {
              found = 1;
              break;
            }
          prev_p = p;
          p = p->next;
        }

      if (found == 0)
        {                       /* not found add to the last */
          prev_p->next = cdk_calloc (1, sizeof (struct verify_uid));
          if (prev_p->next == NULL)
            return CDK_Out_Of_Core;
          prev_p->next->name = uid;
          if (flag != 0)
            prev_p->next->nsigs++;
        }
      else
        {                       /* found... increase sigs */
          if (flag != 0)
            p->nsigs++;
        }
    }

  return CDK_Success;
}

static void
uid_list_free (struct verify_uid *list)
{
  struct verify_uid *p, *p1;

  p = list;
  while (p != NULL)
    {
      p1 = p->next;
      cdk_free (p);
      p = p1;
    }
}

/* returns non (0) if all UIDs in the list have at least one
 * signature. If the list is empty or no signatures are present
 * a (0) value is returned.
 */
static int
uid_list_all_signed (struct verify_uid *list)
{
  struct verify_uid *p;

  if (list == NULL)
    return 0;

  p = list;
  while (p != NULL)
    {
      if (p->nsigs == 0)
        {
          return 0;
        }
      p = p->next;
    }
  return 1;                     /* all signed */
}

/**
 * cdk_pk_check_sigs:
 * @key: the public key
 * @hd: an optinal key database handle
 * @r_status: variable to store the status of the key
 *
 * Check all signatures. When no key is available for checking, the
 * sigstat is marked as 'NOKEY'. The @r_status contains the key flags
 * which are or-ed or (0) when there are no flags.
 **/
cdk_error_t
cdk_pk_check_sigs (cdk_kbnode_t key, cdk_keydb_hd_t keydb, int *r_status)
{
  cdk_pkt_signature_t sig;
  cdk_kbnode_t node;
  cdk_error_t rc;
  u32 keyid;
  int key_status, is_selfsig = 0;
  struct verify_uid *uid_list = NULL;
  char *uid_name;

  if (!key || !r_status)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }

  *r_status = 0;
  node = cdk_kbnode_find (key, CDK_PKT_PUBLIC_KEY);
  if (!node)
    {
      gnutls_assert ();
      return CDK_Error_No_Key;
    }

  key_status = 0;
  /* Continue with the signature check but adjust the
     key status flags accordingly. */
  if (node->pkt->pkt.public_key->is_revoked)
    key_status |= CDK_KEY_REVOKED;
  if (node->pkt->pkt.public_key->has_expired)
    key_status |= CDK_KEY_EXPIRED;
  rc = 0;

  keyid = cdk_pk_get_keyid (node->pkt->pkt.public_key, NULL);
  for (node = key; node; node = node->next)
    {
      if (node->pkt->pkttype != CDK_PKT_SIGNATURE)
        continue;
      sig = node->pkt->pkt.signature;
      rc = _cdk_pk_check_sig (keydb, key, node, &is_selfsig, &uid_name);

      if (rc && rc != CDK_Error_No_Key)
        {
          /* It might be possible that a single signature has been
             corrupted, thus we do not consider it a problem when
             one ore more signatures are bad. But at least the self
             signature has to be valid. */
          if (is_selfsig)
            {
              key_status |= CDK_KEY_INVALID;
              break;
            }
        }

      _cdk_log_debug ("signature %s: signer %08X keyid %08X\n",
                      rc == CDK_Bad_Sig ? "BAD" : "good",
                      (unsigned int) sig->keyid[1], (unsigned int) keyid);

      if (IS_UID_SIG (sig) && uid_name != NULL)
        {
          /* add every uid in the uid list. Only consider valid:
           * - verification was ok
           * - not a selfsig
           */
          rc =
            uid_list_add_sig (&uid_list, uid_name,
                              (rc == CDK_Success && is_selfsig == 0) ? 1 : 0);
          if (rc != CDK_Success)
            {
              gnutls_assert ();
              goto exit;
            }
        }

    }

  if (uid_list_all_signed (uid_list) == 0)
    key_status |= CDK_KEY_NOSIGNER;
  *r_status = key_status;
  if (rc == CDK_Error_No_Key)
    rc = 0;

exit:
  uid_list_free (uid_list);
  return rc;
}


/**
 * cdk_pk_check_self_sig:
 * @key: the key node
 * @r_status: output the status of the key.
 *
 * A convenient function to make sure the key is valid.
 * Valid means the self signature is ok.
 **/
cdk_error_t
cdk_pk_check_self_sig (cdk_kbnode_t key, int *r_status)
{
  cdk_pkt_signature_t sig;
  cdk_kbnode_t node;
  cdk_error_t rc;
  u32 keyid[2], sigid[2];
  int is_selfsig, sig_ok;
  cdk_kbnode_t p, ctx = NULL;
  cdk_packet_t pkt;

  if (!key || !r_status)
    return CDK_Inv_Value;

  cdk_pk_get_keyid (key->pkt->pkt.public_key, keyid);

  while ((p = cdk_kbnode_walk (key, &ctx, 0)))
    {
      pkt = cdk_kbnode_get_packet (p);
      if (pkt->pkttype != CDK_PKT_PUBLIC_SUBKEY
          && pkt->pkttype != CDK_PKT_PUBLIC_KEY)
        continue;

      /* FIXME: we should set expire/revoke here also but callers
         expect CDK_KEY_VALID=0 if the key is okay. */
      sig_ok = 0;
      for (node = p; node; node = node->next)
        {
          if (node->pkt->pkttype != CDK_PKT_SIGNATURE)
            continue;
          sig = node->pkt->pkt.signature;

          cdk_sig_get_keyid (sig, sigid);
          if (sigid[0] != keyid[0] || sigid[1] != keyid[1])
            continue;
          /* FIXME: Now we check all self signatures. */
          rc = _cdk_pk_check_sig (NULL, p, node, &is_selfsig, NULL);
          if (rc)
            {
              *r_status = CDK_KEY_INVALID;
              return rc;
            }
          else                  /* For each valid self sig we increase this counter. */
            sig_ok++;
        }

      /* A key without a self signature is not valid. At least one
       * signature for the given key has to be found.
       */
      if (!sig_ok)
        {
          *r_status = CDK_KEY_INVALID;
          return CDK_General_Error;
        }
    }

  /* No flags indicate a valid key. */
  *r_status = CDK_KEY_VALID;

  return 0;
}
